package simpledb.index;

import simpledb.common.DbException;
import simpledb.storage.Page;
import simpledb.transaction.TransactionId;

import java.io.*;

/**
 * BTreeRootPtrPage 用于存储 B+ 树的根节点指针信息，实现了 BufferPool 使用的 Page 接口。
 */
public class BTreeRootPtrPage implements Page {

    // 页面大小：9 字节（固定）
    public final static int   PAGE_SIZE = 9;

    // 当前页面是否为脏页
    private boolean           dirty     = false;
    // 修改该页面的事务 ID
    private TransactionId     dirtier   = null;

    // 页面唯一标识符
    private final BTreePageId pid;

    // 根节点的页面编号
    private int               root;
    // 根节点类型（内部节点或叶子节点）
    private int               rootCategory;
    // 头部页面的编号（Header Page，用于管理空闲空间等信息）
    private int               header;

    // 页面原始数据备份，用于回滚操作
    private byte[]            oldData;

    /**
     * 构造函数。从磁盘读取的字节数组构造一个 BTreeRootPtrPage。
     *
     * 页面格式：
     * - 整型（4 字节）：根节点页面编号
     * - 字节（1 字节）：根节点类型（叶子或内部节点）
     * - 整型（4 字节）：第一个头部页面编号
     *
     * @param id 页面的唯一标识符
     * @param data 从磁盘读取的页面字节数据
     * @throws IOException 如果读取过程中发生 IO 错误
     */
    public BTreeRootPtrPage(BTreePageId id, byte[] data) throws IOException {
        this.pid = id;
        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data));

        // 读取根节点编号
        root = dis.readInt();
        // 读取根节点类型
        rootCategory = dis.readByte();
        // 读取头节点编号
        header = dis.readInt();

        setBeforeImage(); // 设置旧数据快照
    }

    /**
     * 保存当前页面数据的快照，用于事务回滚。
     */
    public void setBeforeImage() {
        oldData = getPageData().clone();
    }

    /**
     * 返回与此页面关联的页面 ID。
     */
    public BTreePageId getId() {
        return pid;
    }

    /**
     * 每个表只有一个 BTreeRootPtrPage 实例。
     * 此静态方法用于获取给定表的根指针页面 ID（page number = 0）。
     *
     * @param tableid 表 ID
     * @return 给定表的根指针页面 ID
     */
    public static BTreePageId getId(int tableid) {
        return new BTreePageId(tableid, 0, BTreePageId.ROOT_PTR);
    }

    /**
     * 将当前页面内容序列化为字节数组，用于写入磁盘。
     *
     * 保证可以将生成的字节数组传给构造函数重新创建相同的页面对象。
     *
     * @return 页面对应的字节数组
     */
    public byte[] getPageData() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream(PAGE_SIZE);
        DataOutputStream dos = new DataOutputStream(baos);

        try {
            dos.writeInt(root); // 写入根节点编号
            dos.writeByte((byte) rootCategory); // 写入根节点类型
            dos.writeInt(header); // 写入头节点编号
            dos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return baos.toByteArray();
    }

    /**
     * 静态方法，生成一个空白的 BTreeRootPtrPage 对应的字节数组。
     * 用于向文件中添加新、空页面。
     *
     * @return 初始化为空的字节数组
     */
    public static byte[] createEmptyPageData() {
        return new byte[PAGE_SIZE]; // 全 0 填充
    }

    /**
     * 标记此页面是否为脏页，并记录修改它的事务 ID。
     */
    public void markDirty(boolean dirty, TransactionId tid) {
        this.dirty = dirty;
        if (dirty)
            this.dirtier = tid;
    }

    /**
     * 返回最后修改此页面的事务 ID。
     * 如果页面未被修改，返回 null。
     */
    public TransactionId isDirty() {
        if (this.dirty)
            return this.dirtier;
        else
            return null;
    }

    /**
     * 返回此页面在修改前的状态（用于恢复操作）。
     */
    public BTreeRootPtrPage getBeforeImage() {
        try {
            return new BTreeRootPtrPage(pid, oldData);
        } catch (IOException e) {
            e.printStackTrace();
            // 不可能发生：之前已经成功解析过
            System.exit(1);
        }
        return null;
    }

    /**
     * 获取当前 B+ 树的根节点页面 ID。
     * 如果根节点编号为 0，则返回 null。
     *
     * @return 根节点的页面 ID
     */
    public BTreePageId getRootId() {
        if (root == 0) {
            return null;
        }
        return new BTreePageId(pid.getTableId(), root, rootCategory);
    }

    /**
     * 设置当前 B+ 树的根节点页面 ID。
     *
     * @param id 新的根节点页面 ID
     * @throws DbException 如果提供的 ID 不合法
     */
    public void setRootId(BTreePageId id) throws DbException {
        if (id == null) {
            root = 0; // 空根
        } else {
            if (id.getTableId() != pid.getTableId()) {
                throw new DbException("设置根节点时表 ID 不匹配");
            }
            if (id.pgcateg() != BTreePageId.INTERNAL && id.pgcateg() != BTreePageId.LEAF) {
                throw new DbException("根节点必须是内部节点或叶子节点");
            }
            root = id.getPageNumber(); // 设置页面号
            rootCategory = id.pgcateg(); // 设置页面类型
        }
    }

    /**
     * 获取第一个头部页面的 ID。
     * 如果头节点编号为 0，则返回 null。
     *
     * @return 第一个头部页面的 ID
     */
    public BTreePageId getHeaderId() {
        if (header == 0) {
            return null;
        }
        return new BTreePageId(pid.getTableId(), header, BTreePageId.HEADER);
    }

    /**
     * 设置第一个头部页面的 ID。
     *
     * @param id 新的头部页面 ID
     * @throws DbException 如果提供的 ID 不合法
     */
    public void setHeaderId(BTreePageId id) throws DbException {
        if (id == null) {
            header = 0; // 无头部页面
        } else {
            if (id.getTableId() != pid.getTableId()) {
                throw new DbException("设置头节点时表 ID 不匹配");
            }
            if (id.pgcateg() != BTreePageId.HEADER) {
                throw new DbException("头节点必须是 HEADER 类型");
            }
            header = id.getPageNumber(); // 设置页面号
        }
    }

    /**
     * 获取根指针页面的标准大小（固定为 9 字节）。
     *
     * @return 页面大小
     */
    public static int getPageSize() {
        return PAGE_SIZE;
    }
}
